package com.jeasonzhao.commons.parser.expression;

import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.ConvertEx;
import com.jeasonzhao.commons.utils.DataTypes;

public class SyntaxOperationNode extends SyntaxNode
{
    private SyntaxNode left = null;
    private SyntaxNode right = null;
    private String operator=null;
    public SyntaxOperationNode(SyntaxNode l,String op,SyntaxNode r)
    {
        this.operator=op;
        this.left=l;
        this.right=r;
    }
    public String getExpression()
        {
            String ret="";
            if(null!=this.left)
            {
                ret+=this.left;
            }
            if(null!=this.operator)
            {
                ret+=this.operator;
            }
            if(null!=this.right)
            {
                ret+=this.right.getExpression();
            }
            return ret;
    }

    public ValuePair eval(IEvalProvider provider)
        throws EvalException
    {
        this.operator=null==this.operator?null:this.operator.trim();
        ValuePair vLeft = null == this.left ? null : this.left.eval(provider);
        ValuePair vRight = null == this.right ? null : this.right.eval(provider);
        ValuePair ret = processEvalOperation(vRight,vLeft);
        return ret;
    }

    protected ValuePair processEvalOperation(ValuePair vRight,ValuePair vLeft)
        throws EvalException
    {
        ValuePair returnValue = null;
        if(this.operator==null||this.operator.trim().length()<1)
        {
            return ValuePair.NULL;
        }
        else if("+,-,*,/,%,".indexOf(this.operator+ ",") >= 0)
        {
            returnValue = evalMathOperation(vLeft,vRight,this.operator);
        }
        else if("||,&&,and,or,".toLowerCase().indexOf(this.operator.toLowerCase() + ",") >= 0)
        {
            returnValue = evalLogicOperation(vLeft,vRight,this.operator);
        }
        else if(">=,<=,==,!=,<>,>,<,not,equals,=,".toLowerCase().indexOf(this.operator.toLowerCase() + ",") >= 0)
        {
            returnValue = evalCompareOperation(vLeft,vRight,this.operator);
        }
        else
        {
            throw new EvalException(/*this.getExpression() +*/ " does not support the operator: " + this.operator);
        }
//        System.out.println(this.getExpression()+"="+returnValue);
        return returnValue;
    }

    protected ValuePair evalCompareOperation(ValuePair vLeft,ValuePair vRight,String strOperation)
        throws EvalException
    {
        validNeed2LeafNode(vLeft,vRight);

        if("==,!=,<>,".indexOf(strOperation + ",") >= 0
           && false == ((vLeft.getDataType().equals(DataTypes.DOUBLE) || vLeft.getDataType().equals(DataTypes.INT)) &&
                        (vRight.getDataType().equals(DataTypes.DOUBLE) || vRight.getDataType().equals(DataTypes.INT))))
        {
            String a = Algorithms.toString(vLeft.getValue());
            String b = Algorithms.toString(vRight.getValue());
            if("==".equals(strOperation) || "=".equals(strOperation))
            {
                return new ValuePair(Boolean.valueOf(a.equals(b)));
            }
            else
            {
                return new ValuePair(Boolean.valueOf(false == a.equals(b)));
            }
        }
        else if((vLeft.getDataType().equals(DataTypes.DOUBLE) || vLeft.getDataType().equals(DataTypes.INT)) &&
                (vRight.getDataType().equals(DataTypes.DOUBLE) || vRight.getDataType().equals(DataTypes.INT)))
        {
            double f = ConvertEx.toDouble(vLeft.getValue());
            double r = ConvertEx.toDouble(vRight.getValue());
            boolean b = false;
            if(strOperation.equals(">="))
            {
                b = f >= r;
            }
            else if(strOperation.equals("<="))
            {
                b = f <= r;
            }
            else if(strOperation.equals(">"))
            {
                b = f > r;
            }
            else if(strOperation.equals("<"))
            {
                b = f < r;
            }
            else if(strOperation.equals("==") || strOperation.equalsIgnoreCase("equals"))
            {
                b = f == r;
            }
            else if(strOperation.equals("!=") || strOperation.equals("<>") || strOperation.equalsIgnoreCase("not"))
            {
                b = f != r;
            }
            else
            {
                throw new EvalException(/*this.getExpression() +*/ " does not support the logic operator [" + strOperation + "]");
            }
            return new ValuePair(Boolean.valueOf(b));
        }
        else
        {
            throw new EvalException(/*this.getExpression() +*/ " cannot using math operator between objects[" + strOperation + "]");
        }
    }

    protected ValuePair evalLogicOperation(ValuePair vLeft,ValuePair vRight,String strOperation)
        throws EvalException
    {
        validNeed2LeafNode(vLeft,vRight);
        boolean a = ConvertEx.toBool(vLeft.getValue());
        boolean b = ConvertEx.toBool(vRight.getValue());
        boolean c = false;
        if(strOperation.equals("and") || strOperation.equals("&&"))
        {
            c = a && b;
        }
        else if(strOperation.equals("or") || strOperation.equals("||"))
        {
            c = a || b;
        }
        else
        {
            throw new EvalException(/*this.getExpression() +*/ " does not support the logic operator[" + strOperation + "]");
        }
        return new ValuePair(Boolean.valueOf(c));
    }

    protected ValuePair evalMathOperation(ValuePair vLeft,ValuePair vRight,String strOperator)
        throws EvalException
    {
        if(vLeft == null || vRight == null || vLeft.getValue() == null || vRight.getValue() == null)
        {
            if(strOperator.equals("*") || strOperator.equals("/"))
            {
                return new ValuePair(null,DataTypes.DOUBLE);
            }
            else
            {
                return vLeft == null || vLeft.getValue() == null ? vRight : vLeft;
            }
        }
        validNeed2LeafNode(vLeft,vRight);
        DataTypes reType = adjustDataTypes(vRight.getDataType(),vLeft.getDataType());
        if(reType == null)
        {
            throw new EvalException(/*this.getExpression() +*/ " Could not get the return type");
        }
        if(reType.equals(DataTypes.OBJECT) ||
           reType.equals(DataTypes.DATE) ||
           reType.equals(DataTypes.BOOL))
        {
            throw new EvalException(/*this.getExpression() +*/ " [" + reType.getName() + "] cannot using math operator between objects [" + strOperator + "]");
        }
        else if(reType.equals(DataTypes.DOUBLE))
        {
            double a = vLeft.getValue() == null ? 0 : ConvertEx.toDouble(vLeft.getValue());
            double b = vRight.getValue() == null ? 0 : ConvertEx.toDouble(vRight.getValue());
            double ret = -1;
            if(strOperator.equals("+"))
            {
                ret = a + b;
            }
            else if(strOperator.equals("-"))
            {
                ret = a - b;
            }
            else if(strOperator.equals("*"))
            {
                ret = a * b;
            }
            else if(strOperator.equals("/"))
            {
                if(b == 0)
                {
                    throw new EvalException(/*this.getExpression() +*/ " divided zero error");
                }
                ret = a / b;
            }
            else if(strOperator.equals("%"))
            {
                ret = a % b;
            }
            else
            {
                throw new EvalException(/*this.getExpression() +*/ " does not support the math operator[" + strOperator + "]");
            }
            return new ValuePair(new Double(ret));
        }
        else if(reType.equals(DataTypes.INT))
        {
            int a = ConvertEx.toInt(vLeft.getValue());
            int b = ConvertEx.toInt(vRight.getValue());
            int ret = -1;
            if(strOperator.equals("+"))
            {
                ret = a + b;
            }
            else if(strOperator.equals("-"))
            {
                ret = a - b;
            }
            else if(strOperator.equals("*"))
            {
                ret = a * b;
            }
            else if(strOperator.equals("/"))
            {
                if(b == 0)
                {
                    throw new EvalException(/*this.getExpression() +*/ " divided zero");
                }
                double retx = ((double) a) / b;
                return new ValuePair(new Double(retx));
            }
            else if(strOperator.equals("%"))
            {
                ret = a % b;
            }
            else
            {
                throw new EvalException(/*this.getExpression() +*/ " does not support the logic operator[" + strOperator + "]");
            }
            return new ValuePair(Integer.valueOf(ret));
        }
        else if(reType.equals(DataTypes.STRING))
        {
            if(strOperator.equals("+"))
            {
                return new ValuePair(Algorithms.toString(vLeft.getValue()) +
                                     Algorithms.toString(vRight.getValue()));
            }
            else
            {
                throw new EvalException(/*this.getExpression() +*/ " cannot using math operator between objects[" + strOperator + "]");
            }
        }
        else
        {
            throw new EvalException(/*this.getExpression() +*/ " Can not judge the data type of [" + reType.getName() + ":" + reType.getId() + "]");
        }
    }

    private DataTypes adjustDataTypes(DataTypes dt1,DataTypes dt2)
    {
        if(dt1 == null || dt2 == null)
        {
            return dt1 == null ? dt2 : dt1;
        }
        DataTypes rt = dt1.getId() < dt2.getId() ? dt1 : dt2;
//        System.out.println("adjust    "+dt1+"  "+dt2+"==="+rt);
        return rt;
    }

    protected void validNeed2LeafNode(ValuePair v1,ValuePair v2)
        throws EvalException
    {
        if(null == v1)
        {
            throw new EvalException("Need left node for " + this.operator);
        }
        if(null == v2)
        {
            throw new EvalException("Need right node for " + this.operator);
        }
    }
    public void replaceVariableName(INameConvertor map)
    {
        if(null==map)
        {
            return;
        }
        if(null!=this.left)
        {
            this.left.replaceVariableName(map);
        }
        if(null!=this.right)
        {
            this.right.replaceVariableName(map);
        }
    }
}
